/**
 * \file agp.cc
 * \brief AGP initialization.
 * \author Corey Tabaka
 *
 * Provides AGP detection and configuration support for the display module.
 */

/*
   Copyright 2007 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <platform/Pci.h>
#include <Log.h>
#include <stdio.h>

#if 1
#define OUT(msg, args...) Log::printf(LOG_LOW, msg,##args)
#else
#define OUT printf
#endif

// AGP status and command register bits
#define _AGP_RATE_MASK	0x00000007
#define _AGP_RATE_1X	0x00000001
#define _AGP_RATE_2X	0x00000002
#define _AGP_RATE_4X	0x00000004
#define _AGP_FW			0x00000010
#define _AGP_4G			0x00000020
#define _AGP_EN			0x00000100
#define _AGP_SBA		0x00000200
#define _AGP_RQ_MASK	0xff000000

static bool checkAGPCaps(pci_location_t &loc, uint8 &agpCaps);

/**
 * \brief Initializes system AGP.
 *
 * Locates and configures AGP target and master if both are found. 
 */
bool initAGP(void) {
	pci_location_t target, master;
	uint8 targetCaps, masterCaps;
	bool foundTarget = false, foundMaster = false;
	int ret;
	
	// find AGP target (host-PCI or PCI-PCI bridge with AGP capability)
	int index = 0;
	uint32 classCode = 0x060000;
	do {
		ret = Pci::findClassCode(target, classCode, index);
		
		if (ret == _PCI_SUCCESSFUL && checkAGPCaps(target, targetCaps)) {
			OUT("Found AGP target\n");
			foundTarget = true;
			break;
		} else if (ret == _PCI_DEVICE_NOT_FOUND) {
			// see if host-PCI failed and try PCI-PCI
			if (classCode == 0x060000) {
				classCode = 0x060400;
				index = 0;
			} else {
				// already tried host-PCI and PCI-PCI, so fail
				break;
			}
		} else if (ret == _PCI_FUNC_NOT_SUPPORTED) {
			break;
		} else {
			// continue to the next possible AGP target
			index++;
		}
	} while (true);
	
	// find AGP master (display card with AGP capability)
	index = 0;
	do {
		ret = Pci::findClassCode(master, 0x030000, index);
		
		if (ret == _PCI_SUCCESSFUL && checkAGPCaps(master, masterCaps)) {
			OUT("Found AGP master\n");
			foundMaster = true;
			break;
		} else if (ret == _PCI_DEVICE_NOT_FOUND
			|| ret == _PCI_FUNC_NOT_SUPPORTED) {
			break;
		} else {
			index++;
		}
	} while (true);
	
	if (foundTarget && foundMaster) {
		OUT("Found AGP target and master, enabling AGP operation\n");
		
		uint32 tStatus;
		uint32 mStatus;
		
		Pci::readConfig32(target, targetCaps + 0x04, &tStatus);
		Pci::readConfig32(master, masterCaps + 0x04, &mStatus);
		
		// get the common settings
		uint32 common = tStatus & mStatus;
		uint32 rate = common & _AGP_RATE_MASK;
		uint32 fw = common & _AGP_FW;
		uint32 _4g = common & _AGP_4G; // unused for now
		uint32 sba = common & _AGP_SBA;
		
		// find min(targetRQ, masterRQ)
		uint32 rq = (tStatus & _AGP_RQ_MASK) < (mStatus & _AGP_RQ_MASK) ?
			(tStatus & _AGP_RQ_MASK) : (mStatus & _AGP_RQ_MASK);
		
		// find the max common rate
		uint32 setRate = 0;
		if (rate & _AGP_RATE_4X) {
			setRate = _AGP_RATE_4X;
		} else if (rate & _AGP_RATE_2X) {
			setRate = _AGP_RATE_2X;
		} else if (rate & _AGP_RATE_1X) {
			setRate = _AGP_RATE_1X;
		}
		
		// configure the target first
		Pci::writeConfig32(target, targetCaps + 0x08,
			(setRate | fw | sba | _AGP_EN));
		
		// now configure the master
		Pci::writeConfig32(master, masterCaps + 0x08,
			(setRate | fw | sba | rq | _AGP_EN));
		
		uint32 tCommand;
		uint32 mCommand;
		
		Pci::readConfig32(target, targetCaps + 0x08, &tCommand);
		Pci::readConfig32(master, masterCaps + 0x08, &mCommand);
		
		OUT("AGP target command: %08x\n", tCommand);
		OUT("AGP master command: %08x\n", mCommand);
		
		return true;
	} else {
		return false;
	}
}			

static bool checkAGPCaps(pci_location_t &loc, uint8 &agpCaps) {
	bool ret = false;
	
	uint16 status;
	Pci::readConfig16(loc, PCI_CONFIG_STATUS, &status);
	
	if (status & 0x0010) {
		uint8 index;
		Pci::readConfig(loc, PCI_CONFIG_CAPABILITIES, &index);
		
		pci_capability_t cap;
		while (index) {
			Pci::readConfig16(loc, index, (uint16 *) &cap);
			
			switch (cap.id) {
				case 0x02: { // AGP
					// save capability offset
					agpCaps = index;
					
					struct {
						uint8 minor : 4;
						uint8 major : 4;
					} rev;
					
					Pci::readConfig(loc, index + 2, (uint8 *) &rev);
					
					OUT("AGP revision %u.%u\n", rev.major, rev.minor);
					
					uint32 status;
					uint32 command;
					
					Pci::readConfig32(loc, index + 0x04, &status);
					Pci::readConfig32(loc, index + 0x08, &command);
					
					OUT("AGP status: %08x command: %08x\n", status, command);
					
					/*if (rev.major >= 3 && config.base_class == 0x06
						&& (config.sub_class == 0x00
						|| config.sub_class == 0x04)) {
						uint32 agpctrl;
						uint16 apsize;
						uint32 gartlo;
						
						Pci::readConfig32(loc, index + 0x10, &agpctrl);
						Pci::readConfig16(loc, index + 0x14, &apsize);
						Pci::readConfig32(loc, index + 0x18, &gartlo);
						
						OUT("AGP Control: %08x\n", agpctrl);
						OUT("Aperture Size: %04x\n", apsize);
						OUT("AGP GART base address lo: %08x\n", gartlo);
					}*/
					
					ret = true;
					goto done;
				}
				break;
			}
			
			index = cap.next;
		}
	}
	
done:
	return ret;
}
